package com.ke.rule.base;

import com.google.common.base.Stopwatch;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;
import org.reflections.Reflections;
import org.reflections.scanners.Scanners;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 功能部件的决策者，按规则条件匹配功能部件
 * 
 * @author guyu
 *
 */
public class FuncUnitDecider {
  private static final Logger logger = LoggerFactory.getLogger(FuncUnitDecider.class);
  
  private static final ConcurrentMap<String, FuncUnitSpecBill> cachedBills = new ConcurrentHashMap<>();
  private static Map<String, List<Class<? extends IFuncUnit>>> funcUnitSubTypes;
  static {
    Stopwatch watch = Stopwatch.createStarted();

    String funcUnitPkg = System.getProperty("drools.func.unit.package", "com.ke");
    Reflections reflections = new Reflections(new ConfigurationBuilder()
        .forPackage(funcUnitPkg)
        .setScanners(Scanners.SubTypes)
        .filterInputsBy(new FilterBuilder().includePattern(".+\\.domain\\..+\\.class")));
    funcUnitSubTypes = reflections.getSubTypesOf(IFuncUnit.class).stream().collect(Collectors.groupingBy(Class::getSimpleName));
    
    logger.info("reflections took {} ms to scan package, producing types: {}, func unit subtypes: {}",
        watch.elapsed(TimeUnit.MILLISECONDS), reflections.getAll(Scanners.SubTypes).size(),
        funcUnitSubTypes.size());
  }
  
  /**
   * Returns the class list with the given string name
   * @param funcUnitName 功能部件名称
   * @return
   */
  private static Class<? extends IFuncUnit> forName(String funcUnitName) {
    List<Class<? extends IFuncUnit>> subclasses = funcUnitSubTypes.get(funcUnitName);
    
    if (null == subclasses) {
      // 测试用例中可能会配置一些并不存在的功能部件，暂不作为异常跑出，业务代码决策表中的部件名称的正确性需要自行保证
      logger.error("FuncUnit({}) not found in classpath", funcUnitName);
      return null;
    }
    
    int size = subclasses.size();
    if (1 != size) {
      throw new IllegalArgumentException(String.format("ambiguous name(%s), found %d class in classpath", funcUnitName, size));
    }
    return (Class<? extends IFuncUnit>) subclasses.get(0);
  }
  
  /**
   * 决策结果
   */
  static class Consequences {
    private List<FuncUnitSpec> funcUnitSpecList;  // 匹配的功能部件规格列表
    private List<String> inheritedExclusions;     // 需要从父类继承排除的部件
    
    Consequences() {
      super();
      this.funcUnitSpecList = new ArrayList<>();
      this.inheritedExclusions = new ArrayList<>();
    }
    
    public List<FuncUnitSpec> getFuncUnits() {
      return funcUnitSpecList;
    }

    public void setFuncUnits(List<FuncUnitSpec> funcUnitSpecList) {
      this.funcUnitSpecList = funcUnitSpecList;
    }

    public List<String> getInheritedExclusions() {
      return inheritedExclusions;
    }

    public void setInheritedExclusions(List<String> inheritedExclusions) {
      this.inheritedExclusions = inheritedExclusions;
    }
    
    @Override
    public String toString() {
      return "Consequences [funcUnitSpecList=" + funcUnitSpecList + ", inheritedExclusions="
          + inheritedExclusions + "]";
    }

    /**
     * 追加新的功能部件
     * @param funcUnitSpec 符合正则表达式的规格文本
     */
    public void include(String funcUnitSpec) {
      FuncUnitSpec newSpec = FuncUnitSpec.parse(funcUnitSpec);
      
      boolean isDuplicated = funcUnitSpecList.contains(newSpec);
      if (isDuplicated) {
        throw new IllegalArgumentException("FuncUnit（" + newSpec.getName() + " added repeatedly");
      }
      
      funcUnitSpecList.add(newSpec);
    }
    
    /**
     * 从功能部件清单中排除已有节点
     * @param simpleNames 以逗号分割的需要排除的类名
     */
    public void exclude(String simpleNames) {
      StringTokenizer tokenizer = new StringTokenizer(simpleNames, ",");
      while (tokenizer.hasMoreTokens()) {
        String name = tokenizer.nextToken().trim();
        if (inheritedExclusions.contains(name)) {
          continue;
        }
        inheritedExclusions.add(name);
      }
    }
    
    /**
     * 获取规则匹配后的功能部件规格
     * @return
     */
    private List<FuncUnitSpec> getInclusions() {
      // 查找子类
      funcUnitSpecList.forEach(spec -> spec.setKlazz(forName(spec.getName())));
      return funcUnitSpecList;
    }
    
    /**
     * 获取规则匹配后需要排除的validator或processor的类名称
     * @return
     */
    private List<Class<? extends IFuncUnit>> getExclusions() {
      // 查找子类
      List<Class<? extends IFuncUnit>> classes = new ArrayList<Class<? extends IFuncUnit>>();
      inheritedExclusions.forEach(name -> classes.add(forName(name)));
      
      return classes;
    }
  }
  
  public static class InclusionExclusion {
    public final List<FuncUnitSpec> inclusions;
    public final List<Class<? extends IFuncUnit>> exclusions;

    public static InclusionExclusion of(final List<FuncUnitSpec> inclusions, final List<Class<? extends IFuncUnit>> exclusions) {
        return new InclusionExclusion(inclusions, exclusions);
    }

    public InclusionExclusion(final List<FuncUnitSpec> inclusions, final List<Class<? extends IFuncUnit>> exclusions) {
        super();
        this.inclusions = inclusions;
        this.exclusions = exclusions;
    }

    public List<FuncUnitSpec> getInclusions() {
      return inclusions;
    }

    public List<Class<? extends IFuncUnit>> getExclusions() {
      return exclusions;
    }
  }
  
  /**
   * 读取决策表，获取预置的待执行的功能部件规格
   * 
   * @param kieBaseName 知识库名称
   * @param ruleTabular 决策表文件
   * @param context 上下文
   * @param isRootNode 是否根实体
   * @return
   */
  public static InclusionExclusion makeFuncUnitSpecList(String kieBaseName, String ruleTabular,
      ServiceMethodContext context, Object value, boolean isRootNode) {
    Stopwatch watch = Stopwatch.createStarted();
    
    String currentEntityName = getSimpleName(value);

    // todo
    MatchingRulesFact fact = new MatchingRulesFact(context.getCurrentStepType(), currentEntityName, value);
    DecisionTables.execute(kieBaseName, ruleTabular, fact, context, value);
    
    Consequences conseq = fact.getConsequences();
    InclusionExclusion ie = InclusionExclusion.of(conseq.getInclusions(), conseq.getExclusions());
    
    if (logger.isDebugEnabled()) {
      logger.debug("getFuncUnitSpecList(kieBaseName: {}, ruleTabular: {}, value: {}, isRootNode: {}) elapsed: {} ms",
          kieBaseName, ruleTabular, value.getClass().getSimpleName() + "@" + System.identityHashCode(value), isRootNode, watch.elapsed(TimeUnit.MILLISECONDS));
    }
    return ie;
  }
  
  /**
   * 获取规则匹配后的的功能部件列表
   * 
   * @param context
   * @param decisionTableFiles 决策表文件
   * @param value 待处理的目标实体，可能是根实体，也可能是子孙实体，受visitor的遍历驱动
   * @param oldValue 待处理的目标实体的老对象（更新场景下，从db中加载而来的对象，若value是根实体则oldValue也是根实体，若value是子孙实体，oldValue则是根据id匹配的子孙实体，可能为空）
   * @param isRootNode 遍历的是否根节点？
   * @return 匹配的功能部件规格列表
   */
  public static FuncUnitSpecBill getMatchedFuncUnitSpecList(ServiceMethodContext context, String[] decisionTableFiles,
      Object value, Object oldValue, boolean isRootNode) {
    Objects.requireNonNull(context, "context");
    Objects.requireNonNull(decisionTableFiles, "decisionTableFiles");
    Objects.requireNonNull(value, "value");
    
    FuncUnitSpecBill bill = null;
    
    String billCacheKey = null;
    if (context.isFuncUnitBillCacheable()) {
      billCacheKey = getBillCacheKey(context, decisionTableFiles, value, isRootNode);
      bill = cachedBills.get(billCacheKey);
    }
    
    if (null == bill) {
      // 允许同一个方法步骤中decision-table针对不同的entity执行多次
      String brkdwnName = Arrays.toString(decisionTableFiles);
      String brkdwnExtension = CallBreakdown.getBreakdownExtension(context, value);
      CallLatency.begin(brkdwnName, brkdwnExtension, CallBreakdown.GROUP_FUNC_UNIT);
      
      bill = new FuncUnitSpecBill(String.format("%s-%s", ClassUtils.getSimpleName(value), context.getCurrentStepType().name()));
      
      // 决策表中声明的功能部件
      for (String dtFile : decisionTableFiles) {
        bill.addSubBill(getFuncUnitSpecList(context.getService().getClass(), dtFile, context, value, isRootNode));
      }
      
      // 按优先级序号排序，空的排在后面
      bill.orderByPriority();
      
      // put to cache
      if (context.isFuncUnitBillCacheable()) {
        cachedBills.put(billCacheKey, bill);
      }
  
      CallLatency.end(brkdwnName, brkdwnExtension);
    }
    return bill;
  }
  
  /**
   * 获取功能部件清单缓存的Key值
   * @param context
   * @param decisionTableFiles
   * @param value
   * @param isRootNode
   * @return
   */
  @SuppressWarnings("rawtypes")
  private static String getBillCacheKey(ServiceMethodContext context, String[] decisionTableFiles,
      Object value, boolean isRootNode) {
    Hasher hasher = Hashing.sha256().newHasher();
    
    hasher.putUnencodedChars(context.getService().getClass().getName());
    hasher.putUnencodedChars(context.getMethodImpl().getClass().getName());
    for (String file : decisionTableFiles) {
      hasher.putUnencodedChars(file);
    }
    if (null != value) {
      hasher.putUnencodedChars(value.getClass().getName());
    }
    hasher.putBoolean(isRootNode);
    
    return hasher.hash().toString();
  }
  
  @SuppressWarnings("unchecked")
  private static FuncUnitSpecBill getFuncUnitSpecList(Class<? extends IApplicationService> serviceKlass,
      String decisionTableFile, ServiceMethodContext context, Object valueBean, boolean isRootNode) {
    String ruleTabularPath = OverwrittenFactory.getDecisionTableFile(serviceKlass, null, decisionTableFile);
    if (StringUtils.isBlank(ruleTabularPath)) {
      if (logger.isDebugEnabled()) {
        logger.debug("{} does not have its own decision-table file({})", serviceKlass.getSimpleName(), decisionTableFile);
      }
    }
    
    // 获取本类的FuncUnit列表
    FuncUnitSpecBill bill = new FuncUnitSpecBill(ruleTabularPath);
    
    // 当前实体没有定义自己的扩展决策表，则完全继承自父类
    // 若存在自己的扩展决策表，且决策表没有明确声明不从父类继承
    if (StringUtils.isBlank(ruleTabularPath) || DecisionTables.isInheritFromParent(ruleTabularPath)) {
      // 递归获取父类的功能部件列表
      Class<?> serviceSuperClass = serviceKlass.getSuperclass();
      if (null != serviceSuperClass && !serviceKlass.equals(ApplicationServiceBase.class)) {
        FuncUnitSpecBill parentBill = getFuncUnitSpecList((Class<? extends IApplicationService>) serviceSuperClass,
            decisionTableFile, context, valueBean, isRootNode);
        bill.inheritFrom(parentBill);
      }
    }
    
    if (StringUtils.isNotBlank(ruleTabularPath)) {
      makeFuncUnitSpecList(bill, getKieBaseName(serviceKlass), ruleTabularPath, context, valueBean, isRootNode);
    }
    return bill;
  }
  
  /**
   * 根据决策表文件计算功能部件清单
   * @param bill
   * @param kbaseName
   * @param ruleTabularPath
   * @param context
   * @param valueBean
   * @param isRootNode
   * @return
   */
  private static void makeFuncUnitSpecList(FuncUnitSpecBill bill, String kbaseName,
      String ruleTabularPath, ServiceMethodContext context, Object valueBean, boolean isRootNode) {
    bill.setDtPath(ruleTabularPath);
    
    InclusionExclusion ie = makeFuncUnitSpecList(kbaseName, ruleTabularPath, context, valueBean, isRootNode);
    
    // 去重(子类override父类属性), 并进行可调用部件的校验
    for (FuncUnitSpec incl : ie.getInclusions()) {
      if (incl.getType() != null) {
        if (!context.getCurrentStepType().isInvokable(incl.getType())) {
          throw new UnsupportedOperationException(
              "The current visitor is not allowed to execute " + incl.getType() + ": "
                  + incl.getName());
        }
        bill.addToAssemblyList(incl);
      }
    }
    // 继承排除
    bill.addToExclusionList(ie.getExclusions());
  }
  
  /**
   * 从方法服务类的类名中提取知识库名称（截取domain.method左侧的第一个单词）
   * @param serviceKlass
   * @return
   */
  private static String getKieBaseName(Class<? extends IApplicationService> serviceKlass) {
    String packageName = serviceKlass.getPackage().getName();
    
    String subPkgName = StringUtils.substringBefore(packageName, ".service");
    subPkgName = StringUtils.removeEnd(subPkgName, ".generated");   // 部分service并没有被重载，仍然在generated中
    String kieBaseName = StringUtils.substringAfterLast(subPkgName, ".");
    
    return StringUtils.isBlank(kieBaseName) ? "defaultKieBase" : kieBaseName;
  }

  /**
   * 获取实体Simple名称(去掉Immutable和Mutable的前缀)
   *
   * @param entity
   * @return
   */
  public static String getSimpleName(Object entity) {
    if (null == entity) {
      return null;
    }
    String simpleName = org.apache.commons.lang3.ClassUtils.getSimpleName(entity);
    return simpleName;
  }
}
